﻿using MIO.Client.Protocal;
using MIO.Client.Util;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace MIO.Client.Ack
{
    class AckSendTask
    {
        private readonly ConcurrentDictionary<String, AckSendTaskEntry> ackId2AckSendTaskEntry = new ConcurrentDictionary<String, AckSendTaskEntry>();
        private Timer timer = null;
        private IOClient client;
        private int maxReSendCount = 1;
        private int checkInterval = 0;
        private int ackTimeout = 3;

        public AckSendTask(IOClient client)
        {
            this.client = client;
            this.maxReSendCount = client.Config.MaxReSendCount;
            this.ackTimeout = client.Config.AckTimeout;
            checkInterval = this.ackTimeout * 100000;
        }

        private void doTaskOnece()
        {
            List<AckSendTaskEntry> lostPacketList = new List<AckSendTaskEntry>();
            try
            {

                foreach (var item in ackId2AckSendTaskEntry)
                {
                    String key = item.Key;
                    Packet p = item.Value.Packet;
                    long t = item.Value.Timestamp;
                    if (p != null && p.Ack)
                    {
                        if (p.RetryCount >= maxReSendCount)
                        {
                            AckSendTaskEntry lostEntry = new AckSendTaskEntry();
                            lostEntry.Packet = p.clone();
                            lostEntry.Timestamp = t;
                            lostPacketList.Add(lostEntry);
                            remove(client,p.AckId);
                        }
                        else
                        {
                            long delta = DateUtil.GetCurrentTimeStamp() - t;
                            if (delta <= client?.RttHandler?.RtoMilliseconds)
                            {

                            }
                            else
                            {
                                client.send(p);
                                p.RetryCount++;
                                item.Value.Timestamp = DateUtil.GetCurrentTimeStamp();
                                client?.RttHandler?.timeout();
                            }
                        }
                    }
                    else
                    {
                        remove(client,key);
                    }
                }
            }
            catch (Exception ex)
            {
            }
            finally
            {
                if (lostPacketList.Count > 0)
                {
                    foreach (var lostPacket in lostPacketList)
                    {
                        client.EventManager.onLostPacket(client, lostPacket.Packet);
                    }
                }
            }
        }




        public AckSendTask startup(bool immediately)
        {
            stop();

            timer = new Timer();
            timer.Interval = immediately ? 1 : checkInterval;
            timer.Elapsed += (sender, e) =>
            {
                doTaskOnece();
            };
            timer.Start();
            return this;
        }

        public void stop()
        {
            if (timer != null)
            {
                try
                {
                    timer.Stop();
                }
                finally
                {
                    timer = null;
                }
            }
        }

        public bool isRunning()
        {
            return timer != null;
        }


        public bool exist(String ackId)
        {
            return ackId2AckSendTaskEntry.ContainsKey(ackId);
        }

        public void put(Packet p)
        {
            if (p == null)
            {
                return;
            }
            if (p.AckId == null)
            {
                return;
            }

            if (!p.Ack)
            {
                return;
            }



            AckSendTaskEntry entry = new AckSendTaskEntry();
            entry.Packet = p.clone();
            entry.Timestamp = DateUtil.GetCurrentTimeStamp();
            ackId2AckSendTaskEntry.TryAdd(p.AckId, entry);
        }

        public void remove(IOClient client,String ackId)
        {
            AckSendTaskEntry entry = null;
            if (ackId2AckSendTaskEntry.TryRemove(ackId, out entry))
            {
                if (entry.Packet.RetryCount == 0 && client!=null)
                {
                    client?.RttHandler?.stop();
                }
            }
        }


    }
}
